#include "testbasiclighting.h"

#include "inputs/manager.h"
#include "geometrybuilder.h"

namespace test {
	namespace graffiti {

bool BasicLighting::setup(dw::graffiti::RenderWindow* renderWindow)
{
	if (base::setup(renderWindow))
	{
		dw::inputs::InputManager& inputManager = dw::inputs::InputManager::getInstance();
	
		m_toggleRotation.addCondition(dw::inputs::KeyStateCondition(OIS::KC_SPACE, dw::inputs::ButtonStateCondition::Pressed));
		m_switchLighting.addCondition(dw::inputs::KeyStateCondition(OIS::KC_L, dw::inputs::ButtonStateCondition::Pressed));
		m_reloadPrograms.addCondition(dw::inputs::KeyStateCondition(OIS::KC_R, dw::inputs::ButtonStateCondition::Pressed));
		m_reloadPrograms.addCondition(dw::inputs::KeyStateCondition(OIS::KC_LCONTROL, dw::inputs::ButtonStateCondition::Down));
		m_reloadPrograms.addCondition(dw::inputs::KeyStateCondition(OIS::KC_LSHIFT, dw::inputs::ButtonStateCondition::Down));
	
		inputManager.registerCommand(&m_toggleRotation);
		inputManager.registerCommand(&m_switchLighting);
		inputManager.registerCommand(&m_reloadPrograms);

		initPrograms();
		initTriCube();
		initIco();

		m_timer.reset();

		return true;
	}

	return false;
}

bool BasicLighting::render()
{
	bool baseSucceed = base::render();

	dw::float32 deltaT = (dw::float32)m_timer.getSeconds();
	m_timer.reset();

	if (m_toggleRotation.isActive())
	{
		m_rotate = !m_rotate;
	}

	if (m_switchLighting.isActive())
	{
		m_lightingMode = (m_lightingMode + 1) % ProgramsCount;
	}

	if (m_reloadPrograms.isActive())
	{
		delete m_programLighting[0];
		delete m_programLighting[1];
		delete m_programLighting[2];

		initPrograms();
	}

	if (m_rotate)
	{
		m_objectRot = m_objectRot + (deltaT * 90.0f);
	}
	
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	glEnable(GL_CULL_FACE);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	dw::graffiti::Renderer& renderer = dw::graffiti::Renderer::getInstance();

	renderer.pushMatrix(dw::graffiti::matrix::proj);
	renderer.setMatrix(dw::graffiti::matrix::proj, getProjectionMatrix());

	renderer.pushMatrix(dw::graffiti::matrix::view);
	renderer.setMatrix(dw::graffiti::matrix::view, getViewMatrix());

	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.setMatrix(dw::graffiti::matrix::model, glm::mat4(1.0f));

	//! @name Cube
	//! @{
	glm::mat4 objectRotation = glm::rotate(glm::mat4(1.0f), m_objectRot, glm::vec3(0.0f, 1.0f, 0.0f));
	glm::mat4 objectTranslation = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f));
	
	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.multMatrix(dw::graffiti::matrix::model, objectTranslation);
	renderer.multMatrix(dw::graffiti::matrix::model, objectRotation);

	renderer.setProgram(m_programLighting[m_lightingMode]);
	renderer.setUniform("View", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::view)));

	renderer.setUniform("Model", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::model)));
	renderer.setUniform("ModelView", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelview)));
	renderer.setUniform("ModelViewProj", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelviewproj)));

	renderer.setVertexBuffer(m_triCubeVertexBuffer);
	renderer.setIndexBuffer(m_triCubeIndexBuffer);
	renderer.drawIndexed(dw::graffiti::primitive::type::triangles, 12);

	renderer.setProgram(NULL);

	renderer.popMatrix(dw::graffiti::matrix::model);
	//! @}

	//! @name Ico
	//! @{
	objectRotation = glm::rotate(glm::mat4(1.0f), m_objectRot, glm::vec3(0.0f, 1.0f, 0.0f));
	objectTranslation = glm::translate(glm::mat4(1.0f), glm::vec3(2.0f, 0.0f, 0.0f));
	
	renderer.pushMatrix(dw::graffiti::matrix::model);
	renderer.multMatrix(dw::graffiti::matrix::model, objectTranslation);
	renderer.multMatrix(dw::graffiti::matrix::model, objectRotation);

	renderer.setProgram(m_programLighting[m_lightingMode]);
	renderer.setUniform("Model", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::model)));
	renderer.setUniform("ModelView", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelview)));
	renderer.setUniform("ModelViewProj", glm::value_ptr(renderer.getMatrix(dw::graffiti::matrix::modelviewproj)));

	renderer.setVertexBuffer(m_icoVertexBuffer);
	renderer.setIndexBuffer(m_icoIndexBuffer);
	renderer.drawIndexed(dw::graffiti::primitive::type::triangles, 20);

	renderer.setProgram(NULL);

	renderer.popMatrix(dw::graffiti::matrix::model);
	//! @}

	renderer.popMatrix(dw::graffiti::matrix::model);
	renderer.popMatrix(dw::graffiti::matrix::view);
	renderer.popMatrix(dw::graffiti::matrix::proj);

	return baseSucceed;
}

bool BasicLighting::teardown()
{
	dw::inputs::InputManager& inputManager = dw::inputs::InputManager::getInstance();
	
	inputManager.unregisterCommand(&m_toggleRotation);
	inputManager.unregisterCommand(&m_switchLighting);
	inputManager.unregisterCommand(&m_reloadPrograms);

	delete m_triCubeIndexBuffer;
	delete m_triCubeVertexBuffer;

	delete m_icoIndexBuffer;
	delete m_icoVertexBuffer;

	delete m_programLighting[0];
	delete m_programLighting[1];
	delete m_programLighting[2];

	return base::teardown();
}

static const dw::graffiti::AttributeDeclaration attributeDeclarations[] = {
	{ dw::graffiti::attribute::semantic::position, 0, dw::graffiti::attribute::type::vec3 },
	{ dw::graffiti::attribute::semantic::normal, 0, dw::graffiti::attribute::type::vec3 }
};

bool BasicLighting::initTriCube()
{
	dw::uint32 vertexCount;
	void* vertexData;
	dw::uint32 indexCount;
	dw::byte* indexData;

	dw::graffiti::GeometryBuilder::buildFilledCube(attributeDeclarations, 2, vertexData, vertexCount, indexData, indexCount);

	m_triCubeIndexBuffer = new dw::graffiti::IndexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, dw::graffiti::attribute::type::uint8);
	m_triCubeIndexBuffer->bind();
	m_triCubeIndexBuffer->resize(indexCount);
	m_triCubeIndexBuffer->setData(indexData);
	m_triCubeIndexBuffer->unbind();

	m_triCubeVertexBuffer = new dw::graffiti::VertexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, attributeDeclarations, 2);
	m_triCubeVertexBuffer->bind();
	m_triCubeVertexBuffer->resize(vertexCount);
	m_triCubeVertexBuffer->setData(vertexData);
	m_triCubeVertexBuffer->unbind();

	delete [] vertexData;
	delete [] indexData;

	return true;
}

bool BasicLighting::initIco()
{
	dw::uint32 vertexCount;
	void* vertexData;
	dw::uint32 indexCount;
	dw::byte* indexData;

	dw::graffiti::GeometryBuilder::buildFilledIcosahedron(attributeDeclarations, 2, vertexData, vertexCount, indexData, indexCount);

	m_icoIndexBuffer = new dw::graffiti::IndexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, dw::graffiti::attribute::type::uint8);
	m_icoIndexBuffer->bind();
	m_icoIndexBuffer->resize(indexCount);
	m_icoIndexBuffer->setData(indexData);
	m_icoIndexBuffer->unbind();

	m_icoVertexBuffer = new dw::graffiti::VertexBuffer(dw::graffiti::buffer::update::never, dw::graffiti::buffer::access::draw, attributeDeclarations, 2);
	m_icoVertexBuffer->bind();
	m_icoVertexBuffer->resize(vertexCount);
	m_icoVertexBuffer->setData(vertexData);
	m_icoVertexBuffer->unbind();

	delete [] vertexData;
	delete [] indexData;

	return true;
}

bool BasicLighting::initPrograms()
{
	static const dw::graffiti::ProgramAttributeDeclaration basisProgramAttributes[] = {
		{ "Position", dw::graffiti::attribute::semantic::position, 0, dw::graffiti::attribute::type::vec3, false },
		{ "Normal", dw::graffiti::attribute::semantic::normal, 0, dw::graffiti::attribute::type::vec3, true }
	};

	const std::string vsSource = dw::graffiti::loadFile("shaders/vertex_xform.vs");
	const char* vsCString = vsSource.c_str();

	dw::graffiti::VertexShader vShader(&vsCString, 1);

	const std::string psDotSource = dw::graffiti::loadFile("shaders/basiclighting.fs");
	const char* psDotCString = psDotSource.c_str();

	dw::graffiti::PixelShader pShaderDot(&psDotCString, 1);

	const std::string psPhongSource = dw::graffiti::loadFile("shaders/phonglighting.fs");
	const char* psPhongCString = psPhongSource.c_str();

	dw::graffiti::PixelShader pShaderPhong(&psPhongCString, 1);

	const std::string psCellSource = dw::graffiti::loadFile("shaders/celllighting.fs");
	const char* psCellCString = psCellSource.c_str();

	dw::graffiti::PixelShader pShaderCell(&psCellCString, 1);

	m_programLighting[0] = new dw::graffiti::Program();
	m_programLighting[0]->attach(vShader);
	m_programLighting[0]->attach(pShaderDot);
	m_programLighting[0]->link(basisProgramAttributes, 2);

	m_programLighting[1] = new dw::graffiti::Program();
	m_programLighting[1]->attach(vShader);
	m_programLighting[1]->attach(pShaderPhong);
	m_programLighting[1]->link(basisProgramAttributes, 2);

	m_programLighting[2] = new dw::graffiti::Program();
	m_programLighting[2]->attach(vShader);
	m_programLighting[2]->attach(pShaderCell);
	m_programLighting[2]->link(basisProgramAttributes, 2);

	return true;
}

	} // namespace graffiti
} // namespace test
